﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;


namespace KamLib
{

    public static class QueryLinqHelper
    {

        #region Filter

        /// <summary>
        /// 
        /// <param name="notApply">Если стоит true, то не помечать фильтры</param>
        /// </summary>
        public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source,
            IEnumerable<QueryArgs.Filter> filters, bool notApply, params string[] fields)
        {
            Expression exp = null;
            var param = Expression.Parameter(typeof(TSource), "a");
            if (fields != null && fields.Length == 0) fields = null;

            foreach (var filter in filters.Reverse())
            {
                var name = filter.Name;

                if (filter.Applied || fields != null && !fields.Contains(name)) continue;

                var sprop = typeof(TSource).GetPropertyOrField(name);
                if (sprop == null)
                    throw new Exception("Linq.Filter: the property '" + name + "' is not exist.");
                var type = sprop.ResultType();

                var prop = Expression.PropertyOrField(param, name);

                Expression eq = null;
                switch (filter.Comparison)
                {
                    case QueryArgs.Comparison.Equal:
                        switch (filter.FilterType)
                        {
                            case QueryArgs.FilterType.List:
                                foreach (var lvalue in ((IEnumerable<string>)filter.Value).Reverse())
                                {
                                    var eq2 = Expression.Equal(prop, Expression.Constant(lvalue.To(type), type));
                                    eq = eq == null ? eq2 : Expression.OrElse(eq2, eq);
                                }
                                break;
                            case QueryArgs.FilterType.String:
                                var s = filter.Value as string;
                                if (s != null && (s = s.Trim().ToLower()).isfull())
                                {
                                    MethodInfo like;
                                    if (s[0] == '*' || s[0] == '%')
                                    {
                                        s = s.Substring(1);
                                        like = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                                    }
                                    else
                                        like = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });

                                    var toLower = typeof(string).GetMethod("ToLower", new Type[] { });
                                    eq = Expression.Call(Expression.Call(prop, toLower), like, Expression.Constant(s));
                                }
                                break;
                            default:
                                eq = Expression.Equal(prop, Expression.Constant(filter.Value.To(type), type));
                                break;
                        }
                        break;
                    case QueryArgs.Comparison.GreatOrEqual:
                        eq = Expression.GreaterThanOrEqual(prop, Expression.Constant(filter.Value.To(type), type));
                        break;
                    case QueryArgs.Comparison.LessOrEqual:
                        eq = Expression.LessThanOrEqual(prop, Expression.Constant(filter.Value.To(type), type));
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                if (eq != null)
                {
                    exp = exp == null ? eq : Expression.AndAlso(eq, exp);
                }

                filter.Applied = !notApply;
            }

            return exp == null ? source : source.Where(Expression.Lambda<Func<TSource, bool>>(exp, param));
        }

        public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, QueryArgs e, params string[] fields)
        {
            return e.Filters == null || e.KeyValues != null ? source : source.Filter(e.Filters, false, fields);
        }
        public static IQueryable<TSource> Filter<TSource>(this IEnumerable<TSource> source, QueryArgs e, params string[] fields)
        {
            return source.AsQueryable().Filter(e, fields);
        }

        public static IQueryable<TSource> FilterWithoutApply<TSource>(this IQueryable<TSource> source, QueryArgs e, params string[] fields)
        {
            return e.Filters == null || e.KeyValues != null ? source : source.Filter(e.Filters, true, fields);
        }
        public static IQueryable<TSource> FilterWithoutApply<TSource>(this IEnumerable<TSource> source, QueryArgs e, params string[] fields)
        {
            return source.AsQueryable().FilterWithoutApply(e, fields);
        }

        #endregion


        #region Sort

        public static IQueryable<TSource> Sort<TSource>(this IQueryable<TSource> source,
            IEnumerable<QueryArgs.SortField> sortFields, bool useOrderThen)
        {
            if (sortFields == null) return source;

            foreach (var field in sortFields)
            {
                source = source.Sort(field.Name, field.Dir != QueryArgs.SortDirection.DESC, useOrderThen);
                useOrderThen = true;
            }

            return source;
        }

        public static IEnumerable<TSource> Sort<TSource>(this IEnumerable<TSource> source,
            IEnumerable<QueryArgs.SortField> sortFields, bool useOrderThen)
        {
            if (sortFields == null) return source;

            foreach (var field in sortFields)
            {
                source = source.Sort(field.Name, field.Dir != QueryArgs.SortDirection.DESC, useOrderThen);
                useOrderThen = true;
            }

            return source;
        }


        public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, QueryArgs e)
        {
            return source.Sort(e.SortFields, e.UseOrderAsThen);
        }
        public static IQueryable<TSource> ThenBy<TSource>(this IQueryable<TSource> source, QueryArgs e)
        {
            return source.Sort(e.SortFields, true);
        }

        #endregion


        #region Limit

        public static IQueryable<TSource> Limit<TSource>(this IQueryable<TSource> source, QueryArgs e)
        {
            return source.Limit(e.StartIndex, e.LimitCount);
        }

        #endregion


        #region Where

        public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, QueryArgs e)
        {
            if (e.KeyValues != null)
                source = source.Filter(e.KeyValues);
            else
            {
                if (e.UseFilters)
                    source = source.Filter(e);
                e.TotalCount = source.Count();
            }
            if (e.SortFields.Count > 0)
                source = source.OrderBy(e);

            return source.Limit(e);
        }

        public static IQueryable<TSource> Where<TSource>(this IOrderedQueryable<TSource> source, QueryArgs e)
        {
            e.UseOrderAsThen = true;
            return ((IQueryable<TSource>)source).Where(e);
        }

        public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, QueryArgs e, Action before)
        {
            if (before != null) before();
            return source.Where(e);
        }

        public static IQueryable<TSource> Where<TSource>(this IEnumerable<TSource> source, QueryArgs e)
        {
            return source.AsQueryable().Where(e);
        }
        public static IQueryable<TSource> Where<TSource>(this IEnumerable<TSource> source, QueryArgs e, Action before)
        {
            return source.AsQueryable().Where(e, before);
        }

        #endregion
    }
}